#!/usr/bin/env ruby
# $Id$
# $Revision$

msfbase = __FILE__
while File.symlink?(msfbase)
  msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase))
end

$:.unshift(File.expand_path(File.join(File.dirname(msfbase), '..', 'lib')))
require 'msfenv'

$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']

require 'rex'

if ARGV.length < 1
  $stderr.puts("Usage: #{File.basename($0)} <search item> <length of buffer>")
  $stderr.puts("Default length of buffer if none is inserted: 8192")
  $stderr.puts("This buffer is generated by pattern_create() in the Rex library automatically")
  exit
end

value = ARGV.shift
len   = ARGV.shift || 8192

=begin

Examples:

$ ./tools/pattern_create.rb 128
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae

$ ./tools/pattern_offset.rb 8Ac9
[*] Exact match at offset 86

$ ./tools/pattern_offset.rb 39634138
[*] Exact match at offset 86

$ ./tools/pattern_offset.rb 0x39634138
[*] Exact match at offset 86

$ ./tools/pattern_offset.rb 0x396341FF
[*] No exact matches, looking for likely candidates...
[+] Possible match at offset 86 (adjusted [ little-endian: 199 | big-endian: 18996934 ] ) byte offset 0

$ ./tools/pattern_offset.rb 0x3963FFFF
[*] No exact matches, looking for likely candidates...
[+] Possible match at offset 86 (adjusted [ little-endian: 48839 | big-endian: 19045574 ] )
[ snip ]

$ ./tools/pattern_offset.rb 0xFFFF4138
[*] No exact matches, looking for likely candidates...
[+] Possible match at offset 26 (adjusted [ little-endian: 3332243456 | big-endian: 3351109631 ] )
[+] Possible match at offset 56 (adjusted [ little-endian: 3332177920 | big-endian: 3351109375 ] )
[+] Possible match at offset 86 (adjusted [ little-endian: 3332112384 | big-endian: 3351109119 ] )
[ snip ]

=end



# The normal format is a full hexadecimal value: 0x41424344
if (value.length >= 8 and value.hex > 0)
  value = value.hex
# However, you can also specify a four-byte string
elsif (value.length == 4)
  value = value.unpack("V").first
else
# Or even a hex value that isn't 8 bytes long
  value = value.to_i(16)
end

buffer = Rex::Text.pattern_create(len.to_i)
offset = Rex::Text.pattern_offset(buffer, value)

# Handle cases where there is no match by looking for "close" matches
unless offset
  found = false
  $stderr.puts "[*] No exact matches, looking for likely candidates..."

  # Look for shifts by a single byte
  0.upto(3) do |idx|
    0.upto(255) do |c|
      nvb = [value].pack("V")
      nvb[idx, 1] = [c].pack("C")
      nvi = nvb.unpack("V").first

      off = Rex::Text.pattern_offset(buffer, nvi)
      if off
        mle = value - buffer[off,4].unpack("V").first
        mbe = value - buffer[off,4].unpack("N").first
        puts "[+] Possible match at offset #{off} (adjusted [ little-endian: #{mle} | big-endian: #{mbe} ] ) byte offset #{idx}"
        found = true
      end
    end
  end

  exit if found

  # Look for 16-bit offsets
  [0, 2].each do |idx|
    0.upto(65535) do |c|
      nvb = [value].pack("V")
      nvb[idx, 2] = [c].pack("v")
      nvi = nvb.unpack("V").first

      off = Rex::Text.pattern_offset(buffer, nvi)
      if off
        mle = value - buffer[off,4].unpack("V").first
        mbe = value - buffer[off,4].unpack("N").first
        puts "[+] Possible match at offset #{off} (adjusted [ little-endian: #{mle} | big-endian: #{mbe} ] )"
        found = true
      end
    end
  end

end

while offset
  puts "[*] Exact match at offset #{offset}"
  offset = Rex::Text.pattern_offset(buffer, value, offset + 1)
end
